
/* GCSx
** UNDO.H
**
** Editor undo and redo functionality
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

#ifndef __GCSx_UNDO_H_
#define __GCSx_UNDO_H_

class UndoBuffer {
public:
    enum UndoType {
        UNDO_EMPTY = 0,   // An empty chunk at end of undo buffer at all times
        UNDO_PAINTSEL,    // Change of selection; Data1-2 is selection x/y offs, 3 is selection type
                          // Only valid if paint window still open
                          // item1 points to where selection surface is stored
                        
        UNDO_WORLDNAME,
        UNDO_SCENENAME,   // Name changes-
        UNDO_TILENAME,    // Y size 1, X size length of string; X is actual length
        UNDO_LAYERNAME,   // Size is larger of current or new name
        UNDO_ANIMGROUPNAME,
        UNDO_SCRIPTNAME,
        UNDO_SPAWNNAME,   // Spawn includes layer in subid (0 for non-scene spawn)
        UNDO_FOLDERNAME,
                        
        UNDO_SCENEDELETE, // Deletions-
        UNDO_TILEDELETE,  // object is kept in item1
        UNDO_LAYERDELETE, // Layer includes scene in subid and pos in x
        UNDO_ANIMGROUPDELETE,
        UNDO_SCRIPTDELETE,
        UNDO_SPAWNDELETE, // Spawn includes layer in subid (0 for non-scene spawn)
        UNDO_FOLDERDELETE,

        UNDO_SCENECREATE, // Creations-
        UNDO_TILECREATE,  // no object, id is only data
        UNDO_LAYERCREATE, // Layer includes scene in subid and pos in x
        UNDO_ANIMGROUPCREATE,
        UNDO_SCRIPTCREATE,
        UNDO_SPAWNCREATE, // Spawn includes layer in subid (0 for non-scene spawn)
        UNDO_FOLDERCREATE,
        
        UNDO_WORLDSTART,  // X is old id
        
        UNDO_FOLDERADD,   // subid item added, data1 blocktype, data2 pos
        UNDO_FOLDERREMOVE,// subid item erase, data1 blocktype, data2 pos
        
        // All spawn undos include layer in subid (0 for non-scene spawn)
        UNDO_SPAWNMOVE,   // X/Y are old position
        UNDO_SPAWNSPRITE, // data1-3 are old data (ids)
        UNDO_SPAWNSCRIPT, // data1 is old script (id)
        
        // For these, changes are stored as a pointer to a list of strings
        // Undo data itself is 3 TextPoint structures (insertion/sel/selend)
        UNDO_SCRIPTINSERT,// Inserting one or more lines of code- xpos where, ypos # of lines
        UNDO_SCRIPTREMOVE,// Deleting one or more lines (same)
        UNDO_SCRIPTMODIFY,// Modifying one or more lines (same)
        UNDO_SCRIPTDEFAULT,

        UNDO_TILE,        // A change to a tile, represented as a blit
        UNDO_TILECOLL,    // Change to collision map (a blit also)
        UNDO_TILESIZE,    // Sizing/count change, old surface/etc stored
                          // X/Y are width/height, data1 is count, data2 maps count
                          // item1 surface item2 fontwidths item3 collmaps
        UNDO_TILEPERLINE, // X is old value
        UNDO_TILETRANS,   // X is old value
        UNDO_TILEGLYPH,   // X is old value

        UNDO_LAYERMOVE,   // id is scene; x/y are the two positions swapped
        UNDO_LAYERTILE,   // A change to a tile layer
        UNDO_LAYERTILEEXT,//A change to a tile layer extended data
        UNDO_LAYERTILEFX, // A change to a tile layer effects data
        UNDO_LAYERTYPE,   // X is old type
        UNDO_LAYERSIZE,   // Sizing change, old data/fx stored
                          // X/Y are width/height
                          // item1 data item2 fx item3 coll
        UNDO_LAYEREXT,    // X is previous Ext setting (UNDO_LAYERSIZE stores data change)
        UNDO_LAYERFX,     // X is previous Fx setting (UNDO_LAYERSIZE stores data change)
        UNDO_LAYERTILESET,// X is previous tileset id

        UNDO_LAYERTILECUR,// Data1 = layersAffect, Data2 = view, Data3 = dim
        UNDO_LAYERTILETMP,// A change to tile layer data within a window, as part of selection        
                          // (item1 points to where data is stored; item2 to fx; item3 coll)
                          // (layers are just "reset", not exchanged, when this is encountered)
                          // h is doubled if fx or coll present; tripled for both (item2/item3 not null)
                          // must ALWAYS be preceded/followed by the next...
        UNDO_LAYERTILESEL,// Change of selection; Data1-2 is selection x/y offs, 3 is selection type
                          // x is in bytes; width rounded to dword
                          // Actual width (in bytes) is in id
                          // Pitch (in bytes) is in subid
                          // Only valid if tile layer edit window still open
                          // item1 points to where selection data is stored
        UNDO_LAYERSPAWNSEL,// Change of spawns selected
                          // item1 points to set<int> where spawn selection is stored
                          // item2 points to a newly allocated set<int> storing undo data
    };

    enum {
        // Undo buffer- sizes in K
        UNDO_DEFAULT_BUFFER = 8192,
        UNDO_MAX_BUFFER = 65536,
    };
    
#ifndef NDEBUG
    static void setUndoThreshold(int level);
#endif

private:
    enum {
        // OR in to denote this chunk and the previous are one operation
        UNDO_MERGE = 128,
    };
    
    // What world we apply to
    class WorldEdit* world;
    
    // Undo buffer
    int requestedSize; // Requested size in K
    int undoSizeAlloc; // Size allocated
    Uint32* undoBuffer; // Allocated data
    int undoPos; // Current undo position- place next undo here or read redo from previous
    
#ifndef NDEBUG
    // Number of actions before we fail further undos; < 0 to disable feature
    static int undoThresh;
#endif
    
    // If we're currently undoing/redoing, which throttles any other undo/redo attempts
    // as we're taking care of it already
    int inUndo;
    int suspendUndo;
    
    // Undo block level and whether we're still waiting for our first item
    int undoBlock;
    int undoBlockDidFirst;
    int undoBlockFirstPos;
    int undoNonUndoable; // If true, user was warned and chose to go ahead
    
    // Undo data chunk header format-
    struct UndoHeader {
        int sourceWindow; // editor window this originated from
        int prevChunkSize; // 0 if first chunk
        UndoType type;
        int id; // Id of item affected
        int subid; // Sub id, such as tile number affected
        int xSize;
        int ySize;
        int xPos;
        int yPos;
        int data1; // Data specific to chunk
        int data2;
        int data3;
        void* item1; // An item that was deleted or swapped goes here
        void* item2; // These are cleared when the undo chunks are discarded
        void* item3;
    };

    static int undoHeaderSize; // Size in Uint32s
    
    // When you undo, the chunk becomes a redo block, swapping new data for old
    // Same format, it's a redo chunk because it's after the undopos pointer instead of before
    
    // Returns pointer to write data if space is now available (empty chunk is
    // moved as needed) NULL if space could not be made available and undo is being skipped
    Uint32* createUndoSpace(UndoType chunkType, int id, int subid, int xPos, int yPos, int xSize, int ySize, Window* srcWin = NULL, Uint32 data1 = 0, Uint32 data2 = 0, Uint32 data3 = 0, void* item1 = NULL, void* item2 = NULL, void* item3 = NULL) throw_Undo;
    // If undo block contains pointers to something, frees it up
    void deleteUndoBlock(int offset);
    // Clears all redo blocks after the current spot
    void clearRedo();
    
    // Some internal undo-block-writing functions
    void storeUndoScript(UndoType type, int id, const std::list<std::string>& source, int lineNum, int count, EditBox* editbox, Window* srcWin) throw_Undo;
    
public:
    UndoBuffer(class WorldEdit* myWorld);
    ~UndoBuffer();
    
    // Size of 0 = no undo; changing size clears buffer!
    // Size is in K; clips to valid range
    int bufferSize() const { return requestedSize; }
    int bufferSize(int requested);
    
    int undoAvailable() const;
    void undoPerform(int doRedo = 0);
    
    int redoAvailable() const;
    void redoPerform();

    // Call if you do any action that you choose not to make undoable
    void clearUndo();
    
    // Call before and after one or more undos to "link" them all as one item
    // Can also be used to allow use of cancelUndoBlock()
    // If the entire block doesn't fit in the buffer, user will be warned one time;
    // if user cancels, anything done so far is undone (without redo) and UndoException is
    // thrown; if user chooses to continue or a non-undoable item occurs, entire block
    // will be non-undoable; nested blocks work, ultimately generating a single block
    void preUndoBlock();
    void postUndoBlock(); // No need to call on UndoException
    // Undoes all of a block started, entirely, removing possibility of redo
    // No need to call postUndoBlock() after this
    // Keep in mind that this will delete created objects, etc.
    void cancelUndoBlock(); 
    // This cancels the last undone item without actually "undo"ing anything
    // removes redo possibility and deletes any objects.
    // @TODO: This hasn't actually been tested yet
    void cancelLastUndo(); 
    // Temporarily disables undo; will clear undo buffer if any undoable events occur
    void disableUndo();
    void enableUndo();
    
    // @TODO: windowIds not all being used as should be
    
    // Various things you can undo
    // Throws UndoException if undo space could not be created and user cancelled as a result
    // Typically, pass frame windows
    void storeUndoWorldStart(int oldStart, Window* srcWin = NULL) throw_Undo;
    void storeUndoTile(int tileSetId, int tileNum, int x, int y, int w, int h, Window* srcWin = NULL) throw_Undo;
    void storeUndoColl(int tileSetId, int collNum, int x, int y, int w, int h, Window* srcWin = NULL) throw_Undo;
    void storeUndoTileGlyph(int tileSetId, int tileNum, int oldWidth, Window* srcWin = NULL) throw_Undo;
    void storeUndoTileSize(int tileSetId, int oldWidth, int oldHeight, int oldCount, int oldMaps, SDL_Surface* surface, Uint32* fontWidths, Uint32* collisionMaps, Window* srcWin = NULL) throw_Undo;
    void storeUndoTilePerLine(int tileSetId, int oldPerLine, Window* srcWin = NULL) throw_Undo;
    void storeUndoTileTrans(int tileSetId, int oldDefaultTransparent, Window* srcWin = NULL) throw_Undo;
    // (does fx/coll as well, together as a block, if fx/coll data present)
    void storeUndoLayerTile(int layerId, int x, int y, int w, int h, Window* srcWin = NULL) throw_Undo;
    // (x/w are in bits; pitch is in bytes; you must ensure x/y/w/h are bound yourself)
    void storeUndoLayerTileSelection(Uint8** selectionData, int x, int y, int w, int h, int pitch, int selectXOffs, int selectYOffs, int selectType, Window* srcWin = NULL) throw_Undo;
    // (x/w/pitch is in U32s; you must ensure x/y/w/h are bound yourself;
    //  only call in conjunction with storeUndoLayerTileSelection)
    void storeUndoLayerTileTemp(Uint32** data, Uint32** dataExt, Uint32** dataFx, int x, int y, int w, int h, int pitch, Window* srcWin = NULL) throw_Undo;
    // Call this before and after any sel/temp change
    void storeUndoLayerTileCur(Uint32 layerAffect, Uint32 layerView, Uint32 layerDim, Window* srcWin = NULL) throw_Undo;
    void storeUndoLayerSpawnSelection(std::set<int>* selectionData, Window* srcWin = NULL) throw_Undo;
    // (doesn't undo any data changes- layer types assumed to be changed only at creation!)
    void storeUndoLayerType(int layerId, int oldType, Window* srcWin = NULL) throw_Undo;
    void storeUndoLayerSize(int layerId, int oldWidth, int oldHeight, Uint32* data, Uint32* dataExt, Uint32* dataFx, Window* srcWin = NULL) throw_Undo;
    void storeUndoLayerExt(int layerId, int oldUsesExt, Window* srcWin = NULL) throw_Undo;
    void storeUndoLayerFx(int layerId, int oldUsesFx, Window* srcWin = NULL) throw_Undo;
    void storeUndoLayerTileSet(int layerId, int oldTileSet, Window* srcWin = NULL) throw_Undo;
    void storeUndoLayerSwap(int sceneId, int layer1, int layer2, Window* srcWin = NULL) throw_Undo;
    void storeUndoScriptModify(int id, const std::list<std::string>& source, int lineNum, int count, class EditBox* editbox, Window* srcWin = NULL) throw_Undo;
    void storeUndoScriptRemove(int id, const std::list<std::string>& source, int lineNum, int count, class EditBox* editbox, Window* srcWin = NULL) throw_Undo;
    void storeUndoScriptInsert(int id, int lineNum, int count, class EditBox* editbox, Window* srcWin = NULL) throw_Undo;
    void storeUndoScriptDefault(int id, int animId, int tileId, int subId, Window* srcWin = NULL) throw_Undo;
    void storeUndoSpawnMove(int id, int layerId, int oldX, int oldY, Window* srcWin = NULL) throw_Undo;
    void storeUndoSpawnSprite(int id, int layerId, int animId, int tileId, int subId, Window* srcWin = NULL) throw_Undo;
    void storeUndoSpawnScript(int id, int layerId, int scriptId, Window* srcWin = NULL) throw_Undo;
    void storeUndoFolderAdd(int id, SaveLoad* item, int pos, Window* srcWin = NULL) throw_Undo;
    void storeUndoFolderRemove(int id, SaveLoad* item, int pos, Window* srcWin = NULL) throw_Undo;
    // (assumes surface is 32bit)
    void storeUndoPaintSelection(struct SDL_Surface** selectionData, int x, int y, int w, int h, int selectXOffs, int selectYOffs, int selectType, Window* srcWin = NULL) throw_Undo;
    // Type is UNDO_FOLDERNAME, UNDO_SPAWNNAME, UNDO_WORLDNAME, UNDO_SCENENAME, UNDO_TILENAME, UNDO_LAYERNAME, UNDO_ANIMGROUPNAME, UNDO_SCRIPTNAME
    void storeUndoName(UndoType type, int id, const std::string& oldName, const std::string& newName, Window* srcWin = NULL) throw_Undo;
    // Type is UNDO_FOLDERDELETE, UNDO_SCENEDELETE, UNDO_TILEDELETE, UNDO_ANIMGROUPDELETE, UNDO_SCRIPTDELETE
    // Object should be the Edit variation; Returns true if undo successsful
    // False means you have to delete the object yourself
    int storeUndoDelete(UndoType type, int id, void* object, Window* srcWin = NULL) throw_Undo;
    int storeUndoLayerDelete(int id, int sceneId, int pos, class LayerEdit* object, Window* srcWin = NULL) throw_Undo;
    int storeUndoSpawnDelete(int id, int layerId, class SpawnEdit* object, Window* srcWin = NULL) throw_Undo;
    // Type is UNDO_FOLDERCREATE, UNDO_SCENECREATE, UNDO_TILECREATE, UNDO_ANIMGROUPCREATE, UNDO_SCRIPTCREATE
    // Returns true if undo stored successful and a later undo will delete the object
    // False means you have to delete the object if a cancel/exception occurs
    int storeUndoCreate(UndoType type, int id, Window* srcWin = NULL) throw_Undo;
    int storeUndoLayerCreate(int id, int sceneId, int pos, Window* srcWin = NULL) throw_Undo;
    int storeUndoSpawnCreate(int id, int layerId, Window* srcWin = NULL) throw_Undo;
    
    // Warn user that something cannot be undone; returns if OK, throws if cancel
    // If user says OK, undo buffer is cleared!
    // If user cancels, any partial undo block is undone!
    void warnUser();
};

#endif

